なぜ型定義で実際に返される型のUnion Typeになっていないのか
前提
Discord.jsのclient.channels.get(id)は本来、TextChannelやDMChannelなどの実際に存在する対象を返す
ただ型定義上は、チャンネルという共通概念であるChannelを返すようになっている
ドキュメント上でも同じようになっているけど、これは普通に書き換えたほうがわかりやすい気がする
実際にはTextChannelであっても、型定義でChannelになっていると、型エラーが出てしまって面倒くさい
TypeScriptを使っていると通常ではなるべく避けるasを使うはめになって、型安全か冗長化を選ばざるを得なくなる
なぜそうなっているのか
調査はしていないけど、ドキュメント上もChannelを返すようになっているのを踏まえると昔からの成り行きっぽい
問題が浮き彫りにならなかったとか、ドキュメント上の型もなんとなく理解できてたとかで
一見問題なさそうに見えたけど、Union Typeのままでメソッド呼び出しができないという問題が発覚してrevertされた TypeScriptの型推論上の問題として、Union Typeのままだとメソッド呼び出しができなくなるという問題があった
https://gyazo.com/46cbdea4387bfbb4dd1455dfba202c56 https://www.typescriptlang.org/play/index.html?ssl=4&ssc=5&pln=4&pc=8#code/MYewdgzgLgBAhgJwQLhtBBLMBzA2gXRgB8YwBXAWwCMBTBAmAXgCgBIAWTigAsA6BOGAAmICgAoAlDAB8MAAy8ArDAD8MXAHIAjBoA0MDQCY9BgMwbCqXFv2H9p-M2aIEvCnAAOYmk1mhIIAA2NLyBINjeEhLMQA
この型推論の挙動はTypeScript 4.0で改善されそうな気配があって、そのうちまたUnion Typeになるかもしれない
なったtig.icon
検証
新たなバージョンがリリースされる前にrevertされて、その変更を含まれたバージョンが存在しない
代わりにGitHubから該当コミットをインストールすることで体験できる:discordjs/discord.js#69d69f2
条件式の中で型が絞り込めるのがわかる(上が通常のバージョンで、下がUnion Typeあり)
https://gyazo.com/ce39edc82a1ed5af2da7a967d3066877
https://gyazo.com/951028027f57affbbbad674cdf777922
挙動の深堀り
https://gyazo.com/770683b99c38e68320d08eb345cd882f
ただ実際に検証してみるとsetNameではなくthenで型エラーが出てることから単にメソッド呼び出しが問題ではなくてもっと複雑な因子が絡んでいそうな感じがした
channel変数はTextChannel | VoiceChannelのような、GuildChannelを継承したクラスのUnion Typeになっていて、それらのクラスにはsetNameが定義されていなく、GuildChannel.setNameという扱いになってる可能性はありそう
つまりUnion Typeでも、それらの共通の親クラスのメソッドとして解決できると対象外となる...?
そしてsetNameを抜けてPromise付きでPromise<TextChannel> | Promise<VoiceChannel>のような各クラスのUnion Typeになって、それぞれ独立した型に型に対するメソッド呼び出しで型エラーが出るといった感じっぽい
回避策
今の段階だと型を補完するために、自分で明示する必要があって、上にも書いたようにasを使う方法がある
ただ闇雲にasを使うと間違った型に変えかねないから、多少冗長にはなってしまうがType Predicateのほうが安全
channel.typeでチャンネルの種類が取得できるのでこのように書ける(もちろんinstanceofを使ってもよい)
NewsChannelなどはTextChannelを継承しているけどtypeは"news"なのでinstanceofではなくてtypeを使ったほうがよい
code:ts
const isTextChannel = (channel: Discord.Channel): channel is Discord.TextChannel => channel.type === 'text'
関連